package com.mokylin.dbschema.service;

import com.mokylin.dbschema.dao.DbVersionDao;
import com.mokylin.dbschema.exception.DbVersionCheckUpdateException;
import com.mokylin.dbschema.exception.DbVersionExecuteException;
import com.mokylin.dbschema.exception.DbVersionIllegalStateException;
import com.mokylin.dbschema.handler.SqlVersionHandler;
import com.mokylin.dbschema.handler.SqlVersionHandlerFactory;
import com.mokylin.dbschema.pojo.DbVersion;
import com.mokylin.dbschema.util.ClassInfoCache;
import com.mokylin.dbschema.util.DBUtils;
import com.mokylin.dbschema.util.GlobalUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.util.LinkedList;
import java.util.List;

/**
 * @author 李朝(Li.Zhao)
 * @since 2015/8/31.
 */

public class DbVersionService {


    private static final Logger LOG = LoggerFactory.getLogger(DbVersionService.class);

    private DbVersionDao dbVersionDao = ClassInfoCache.getSingleton(DbVersionDao.class);
    private SqlVersionHandler sqlVersionHandler = SqlVersionHandlerFactory.getSqlVersionHandler();


    /**
     * 检查是否需要版本升级，如果需要则把老版本信息返回
     *
     * @return
     * @throws DbVersionCheckUpdateException
     */
    public DbVersion check() throws DbVersionCheckUpdateException, DbVersionIllegalStateException {
        DbVersion dbVersion = null;
        try {
            try {
                dbVersion = dbVersionDao.getDbVersion();
                if (dbVersion == null) {// 可能是其他机器刚创建表，未插入初始数据。。
                    LOG.warn("no version data,3 seconds retry");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        LOG.error(e.getMessage(), e);
                    }
                    dbVersion = dbVersionDao.getDbVersion();
                }
                GlobalUtils.setOriginalDbVersion(dbVersion);
                if (dbVersion == null) {//重试后仍然没数据，则插入初始化数据
                    try {
                        dbVersionDao.insert(dbVersion = DbVersion.getInitDbVersion());
                    } catch (SQLSyntaxErrorException e) {
                        throw new DbVersionCheckUpdateException("init insert version data fail");
                    }
                }
                if ((DbVersion.Status.BUILDING.equals(dbVersion.getStatus()) || DbVersion.Status.PREPARED.equals(dbVersion.getStatus()))
                        && DbVersionHeartBeatService.isConnecting()) {//这两种状态，且在心跳时间范围内不能升级（代表其他服务器正在运行升级系统）
                    throw new DbVersionIllegalStateException("wrong status,may be other server is building，version status:{}" + dbVersion.getStatus().name());
                }
                if (DbVersion.Status.ERROR.equals(dbVersion.getStatus())) {//上次升级发生错误，也可能是当前时间其他服务器升级出错了（可能出错版本与当前是同一版本，或者当前版本已经修复错误）
                    LOG.warn("last run db-schema error,version:{},cause by:{},last exec error time:{}", dbVersion.getVersion(), dbVersion.getDesc(), dbVersion.getUpdateTime());
                }
            } catch (SQLSyntaxErrorException e) {//可能是还未创建版本表
                LOG.warn("may be no table t_db_version,create table and init data...:{}", e.getMessage());
                try {
                    try {
                        dbVersionDao.createTable();
                    } catch (SQLSyntaxErrorException e1) {
                        LOG.warn("create version table error:{}", e.getMessage());
                    }
                    dbVersionDao.insert(dbVersion = DbVersion.getInitDbVersion());
                } catch (SQLSyntaxErrorException e1) {
                    throw new DbVersionCheckUpdateException("init version db error, may be other server is building...：" + e.getMessage());
                }
            }
        } catch (SQLException e) {
            LOG.warn(e.getMessage(), e);
            throw new DbVersionCheckUpdateException(e.getMessage(), e);
        }
        return dbVersion;
    }


    /**
     * 运行startVersion后版本的sql<br/>
     * 如果上次构建失败(出错或者异常终止)，则从失败的版本文件开始执行
     *
     * @param startVersion
     * @return
     */
    public boolean buildSqlFilesAfter(DbVersion startVersion) throws DbVersionExecuteException {
        String version = startVersion.getVersion();
        List<File> sortSqlFiles = sqlVersionHandler.getSortSqlFileAfter(version);
        if (!DbVersion.Status.NORMAL.equals(startVersion.getStatus())) {//如果上次构建失败，则从失败的版本文件开始执行
            LOG.info("add unmormal status sql file, version:{}", version);
            File currentVersionSqlFile = sqlVersionHandler.findSqlFileByVersion(version);
            if (currentVersionSqlFile == null) {
                LOG.info("last non-normal version sql file not exist,version:{}", version);
            } else {
                LinkedList<File> tmp;
                if (sortSqlFiles instanceof LinkedList) {
                    tmp = (LinkedList<File>) sortSqlFiles;
                } else {
                    tmp = new LinkedList<>(sortSqlFiles);
                }
                tmp.addFirst(currentVersionSqlFile);
                sortSqlFiles = tmp;
            }
        }
        LOG.info("execute sql file size:{}", sortSqlFiles != null ? sortSqlFiles.size() : 0);
        if (sortSqlFiles==null || sortSqlFiles.isEmpty()
                || !dbVersionDao.forceExec(dbVersionDao::setPreparedStatus)) {
            LOG.info("no execute sql file or convert status to PREPARED status error!!!");
            return true;
        }
        DbVersionHeartBeatService.start();
        for (File file : sortSqlFiles) {
            LOG.info("execute sql file:{}", file.getName());
            dbVersionDao.forceExec(() -> dbVersionDao.updateBuildingStatus(sqlVersionHandler.getFileVersion(file)));
            try {
                DBUtils.executeSqlFile(file);
            } catch (Exception e) {
                throw new DbVersionExecuteException(String.format("execute sql file[%1$s] error:{%2$s}", file.getName(), e.getMessage()));
            }
        }
        return true;
    }

    public void errorEnd(String msg) {
        dbVersionDao.forceExec(() -> dbVersionDao.setErrorStatus(msg));
    }

    public void end() {
        dbVersionDao.forceExec(() -> dbVersionDao.convertBuilding2NormalStatus());
    }

    /**
     * 将上次版本信息记录到数据库
     */
    public void dbLog() {
        DbVersion originalDbVersion = GlobalUtils.getOriginalDbVersion();
        if (originalDbVersion == null) {
            return;
        }
        dbVersionDao.forceExec(() -> dbVersionDao.insertLog(originalDbVersion));
    }

    public DbVersion getDbVersion() throws SQLException {
        return dbVersionDao.getDbVersion();
    }
}